﻿#coding=utf-8
#-*- coding: UTF-8 -*- 
#coder  poorboy(叼毛)
#url  http://www.caipiao365.vip


import urllib
import urllib2
import json 
import random 
import urlparse
import binascii
import base64
import os

# 访问网页的header
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"}
httpBase = "https://www.toutiao.com/search_content/?"

# JS返回的数组名称
JSARRNAME = "JsArr"



class VideoItem(object):
    #视频的标题
    title = ""
    #视频的URL 有两种如下 一种是视频源地址  一种是网页 流媒体的方式
    # url: http://dianbo01.jxtvcn.com.cn/jxwlgbdst/vod/2017/12/24/df325ac6b46b4ddbb9d06bcd0dbf5c1c/h264_800k_mp4.mp4
    #url: http://toutiao.com/group/6495197689683116557/
    url   = ""
    
    #检查是URL看是mp4文件还是
    isMp4File = False

    #初始化方法
    # _title 视频的标题
    # _url   视频的地址    
    def __init__(self, _title, _url):
        self.title = _title
        self.url = _url
        self.ClearTitleErrorStr()

        #要是以.mp4结尾的url那么就是mp4文件
        if self.url[-4:] == ".mp4":
            self.isMp4File = True

    #有时候转码可能出现特殊字符 懒得查原因就这里写个笨一点的方法
    def ClearTitleErrorStr(self):
        self.title =  self.title.replace("?", "")
        self.title =  self.title.replace("%", "")
        self.title =  self.title.replace("`", "")
        self.title =  self.title.replace("'", "")
        self.title =  self.title.replace("$", "")
        self.title =  self.title.replace("*", "")
        self.title =  self.title.replace("^", "")
        self.title =  self.title.replace("&", "")
        self.title =  self.title.replace("(", "")
        self.title =  self.title.replace(")", "")
        self.title =  self.title.replace("\\", "")
        self.title =  self.title.replace("/", "")
        
    
    def IsMp4(self):
        return self.isMp4File
    
class DownVideoMgr(object):
    
    #视频文件保存的路径
    sBaseFilePath = "F:\\vDown\\"
    isFileOver = False
    
    #设置文件的保存目录
    #参数:
    #       sPath      视频文件保存的路径
    #返回值: 无
    def SetDownBaseDir(self, sPath):
        self.sBaseFilePath = sPath

    #设置文件当本地文件存在时采用的操作
    #参数:
    #       _isOver    当本地文件存在时采用的操作 
    #       _isOver = True 覆盖  _isOver =False 跳过
    #返回值: 无
    def SetFileSaveOver(self, _isOver):
        self.isFileOver =  _isOver
        
    #获取搜索URL的参数 
    #参数:
    #       serchStr  搜索的关键字
    #       nPages    搜索的页码
    #返回值:
    #       strParas 通过URL编码后的查询参数
    def GetParas(self, serchStr, nPages):
        '''
        研究了他的URL参数大致猜测的字段和意思
        '''        
        paras = {
            "offset": nPages,             #记录的开始数字 0开始  0 20 40 60 具体见 count 字段  默认是每页有20条记录但是每面多多少少有些广告
            "format": "json",             #返回的数据类型
            "keyword": serchStr,          #要搜索的关键词
            "autoload": "true",           #html网页获取记录后是否自动显示的页面里
            "count": "20",                #每次请求返回的记录大小（建议和前台的页面保持一致） 我设置其他的,他们的的服务器也只每次返回20条记录
            "cur_tab": "2",               #前面页面的标签（可以用2不用管他 和前台的页面保持一致） 
            "from": "search_tab",         #提交搜索的前面页面的表单名（不用管他就用这个 也就是cur_tab 2）
            "callback": JSARRNAME         #Json数据返回后前台页面JS代码里数组的名字这个我改了一个 他的太长了点 看不习惯 @_@
            }
        strParas = urllib.urlencode(paras)
        return strParas


    #获取打开页面的结果
    #参数:
    #       url  打开网页的地址 
    #返回值:
    #       成功 打开页面的结果
    #       失败 空字符串
    def GetWebRet(self, url):
        htmlStr = ""
        try:
            request = urllib2.Request(url, headers=headers)
            response = urllib2.urlopen(request)
            htmlStr = response.read()
        except Exception as e:
            print e
            bRet = False
            htmlStr = ""
        return htmlStr

    #获取搜索的结果
    #参数:
    #       seachKey  搜索的关键词 
    #       nPage     第几页
    #返回值:
    #       成功 搜索的JSON结果
    #       失败 空字符串
    def GetSeachRet(self, seachKey, nPage):        
        url = httpBase + self.GetParas(seachKey, nPage)
        jsonStr = self.GetWebRet(url)
        if (len(jsonStr) > 10):
            jsonStr = jsonStr[len(JSARRNAME) + 1:-1]
        return jsonStr

    #根据KEY返回JSON对象的里值
    # jsObj json对象
    # key   json对象的key    
    #参数:
    #       jsObj json对象
    #       key   json对象的key
    #返回值:
    #       成功 要是KEY存在就返回对应的值
    #       失败 要是KEY不存在返回空字符串
    def GetJsonItemData(self, jsObj, key):
        if jsObj.has_key(key):
            return jsObj[key]
        return ''


    #根据JSON字符串获取视频记录信息
    #参数:
    #       jsonStr json字符串    
    #返回值:
    #       VideoItem  列表
    def GetVItem(self, jsonStr):
        jsObj = json.loads(jsonStr)
        itemsize = len(jsObj['data'])
        videoList = []
        for i in range(0, itemsize): 
            tmpJs = jsObj['data'][i]
            #.decode('raw_unicode-escape').encode('utf-8')
            title = u"" + self.GetJsonItemData(tmpJs, 'title')
            url = u"" +  self.GetJsonItemData(tmpJs, 'url')
            if url.find("wukong.com/question") > 0:
                url = ""

            if len(title) > 0 and len(url) > 0 :
                tItem = VideoItem(title, url)
                videoList.append(tItem);
        return videoList

    #获取搜索的结果
    #参数:
    #       seachKey  搜索的关键词 
    #       nStart    从第几条记录开始获取
    #返回值:
    #        VideoItem  列表  
    def GetVideoItem(self, seachKey, nStart):
        jsonStr = self.GetSeachRet(seachKey, nStart)
        return self.GetVItem(jsonStr)

    #根据url保存文件
    #参数:
    #       url      mp4文件的URL 
    #       loactFile   本地保存的路径
    #返回值:
    #        True    下载成功
    #        False    下载失败
    def SaveUrl2File(self, url, loactFile):  
        bRet = True
        try:
            request = urllib2.Request(url, headers=headers)
            response = urllib2.urlopen(request) 
            data = response.read() 
            with open(loactFile, "wb") as code:   
                code.write(data) 
        except Exception as e:
            print e
            bRet = False
        return bRet


    #根据流媒体的网页地址获取流媒体的srcID
    #参数:
    #       url     流媒体的网页地址
    #返回值:
    #        True    返回流媒体的src
    #        False   返回空字符串
    def GetStreamingVideoID(self, url):
        htmlStr = self.GetWebRet(url)
        if len(htmlStr) < 5:
            return "";
        strID = ""
        strFindSrart = "videoid" 
        nStart = htmlStr.find(strFindSrart) 
        nEnd = -1
        if nStart > 0:
            nEnd = htmlStr.find(",", nStart) 

        if nEnd > 10: 
            strID = htmlStr[nStart+len(strFindSrart)+2 : nEnd-1]         
        
        return strID
        

    #获取随机数    
    #参数: 无 
    #返回值:
    #        随机数字符串
    def GetRandPara(self):
        randStr = bytes(random.random()) [2:]
        return randStr

    #获取CRC检验右移的值  
    #参数:
    #       val    右移的值 
    #        n     右移的值的位数
    #返回值:
    #         右移动的结果
    def GetCrcValue(self, val, n):
        return val >> n if val >= 0 else (val + 0x100000000) >> n

 
    #根据视频的ID获取真实播放地址的API地址
    #参数:
    #       vID        流媒体的ID 
    #       vID 可以通过 GetStreamingVideoID 这个方法获取
    #返回值:
    #        获取真实播放地址的API地址
    def GetStreamAPIUrl(self, vID):
        r = self.GetRandPara()   
        jsUrl = 'http://i.snssdk.com/video/urls/v/1/toutiao/mp4/%s' % vID 
        n = urlparse.urlparse(jsUrl).path + '?r=' + r 
        c = binascii.crc32(n)  
        s = self.GetCrcValue(c, 0)

        return jsUrl + '?r=' + r + '&s=' + bytes(s)

    # 获取真实播放地址的API获取真实的流媒体播放地址
    #参数:
    #       url        流媒体的获取API的地址
    #       url 可以通过 GetStreamAPIUrl 这个方法获取
    
    #返回值:
    #        True    流媒体真实地址
    #        False    空字符串
    def GetStreamSrc(self, url):
        vID = self.GetStreamingVideoID(url)
        src = self.GetStreamAPIUrl(vID)
        jsonStr = self.GetWebRet(src)
		
        vUrl = ""
        strFindSrart = "main_url" 
        nStart = jsonStr.find(strFindSrart) 

        nEnd = -1
        if nStart > 0:
            nEnd = jsonStr.find(",", nStart)

        if nEnd > 10: 
            vUrl = jsonStr[nStart+len(strFindSrart)+2 : nEnd-1]         
        
        videourl = base64.b64decode(vUrl)
        return videourl

    #根据流媒体的地址下载流媒体文件
    #参数:
    #       url        流媒体的Src地址
    #       file     本地保存的路径
    #返回值:
    #        True    下载成功
    #        False    下载失败 
    def DownStreamVideo(self, url, file):
        videoUrl = self.GetStreamSrc(url)
        return self.SaveUrl2File(videoUrl, file)

    #根据VideoItem下载视频
    #参数:
    #       vItem    流媒体信息
    #       可以通过 GetVideoItem 方法获取
    #返回值:
    #        True    下载成功
    #        False    下载失败 
    def DownVideoItem(self, vItem):
        fileSavePaht = self.sBaseFilePath + vItem.title.encode("gbk") + ".mp4"
        if os.path.exists(fileSavePaht):
            if self.isFileOver:
                try:
                    os.remove(fileSavePaht)
                except Exception as e:
                    print e
                    return False
            else:
                return True 

        if vItem.IsMp4(): 
            return self.SaveUrl2File(vItem.url, fileSavePaht)
        else: 
            return  self.DownStreamVideo(vItem.url, fileSavePaht)

        
def Test(): 
    DowObj  = DownVideoMgr()    
    keyS = "儿童舞蹈教学"
    vArrList = []
    for i in range(0, 201, 20):
        vItem = DowObj.GetVideoItem(keyS, i) 
        for r in vItem :
            vArrList.append(r)  

    for r in vArrList :
        DowObj.DownVideoItem(r)
    
if __name__ == '__main__':
    Test() 
 